/*
 * ﻿Copyright (C) 2013-2014 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.injection;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Simple abstract aspect to return different value than one has been held in field or been returned 
 * by (getter) method. <br />
 * This is not injector; just for returning other object than original. <br />
 * An example usage will be to deceive the value of static member field only for certain circumstance 
 * without changing its actual value. 
 * 
 * @author Arata Y.
 */
@Aspect
public abstract class AbstractSwapperAspect {
	private Logger logger = LoggerFactory.getLogger( this.getClass());
		public Logger getLogger() {
			return logger;
		}
		protected void setLogger( final Logger logger) {
			this.logger = logger;
		}

	public static final boolean IsOnlyForNullDefaultValue = true;
		public static boolean getIsOnlyForNullDefaultValue() {
			return IsOnlyForNullDefaultValue;
		}
	private boolean isOnlyForNull 
	= AbstractSwapperAspect.getIsOnlyForNullDefaultValue();
		/**
		 * Getter for value of switch controlling whether returning original object (what will be 
		 * obtained by quoting target field value or target method invocation) or replacement object being 
		 * returned by <code>{@link #getReplacement()}</code> method.<br />
		 * Default to {@value #IsOnlyForNullDefaultValue}.
		 * @return true for returning original object when original object is not null, false for returning 
		 * replacement object being returned by <code>{@link #getReplacement()}</code> method.
		 */
		public boolean isOnlyForNull() {
			return isOnlyForNull;
		}
		/**
		 * Setter for value of switch controlling whether returning original object (what will be 
		 * obtained by quoting target field value or target method invocation) or replacement object being 
		 * returned by <code>{@link #getReplacement()}</code> method.<br />
		 * @param isOnlyForNull : true for returning original object when original object is not null, 
		 * false for returning replacement object being returned by <code>{@link #getReplacement()}</code> 
		 * method.
		 */
		public void setOnlyForNull( boolean isOnlyForNull) {
			this.isOnlyForNull = isOnlyForNull;
		}
		
	/**
	 * To provide alternate object for swapping original one what will be obtained by quoting target 
	 * field value or target method invocation. <br />
	 * Sub (non abstract) aspect must override this.<br />
	 * 
	 * @return Alternate object being swapped for original one.
	 * @see #aroundGettingOriginal(ProceedingJoinPoint)
	 * @see #isOnlyForNull()
	 */
	public abstract Object getReplacement();

	/**
	 * Abstract point-cut for picking up join-point of quoting target field value or target method 
	 * invocation in order to swap its result value.<br />
	 * The result value will be swapped to one being returned by <code>{@link #getReplacement()}</code> 
	 * method in <code>{@link #aroundGettingOriginal(ProceedingJoinPoint)}</code> advise method. <br />
	 * Sub (non-abstract) aspect must override this.
	 */
	@Pointcut
	public abstract void pointcutAtGettingOriginal();
	
	/**
	 * Around advise method to swap result object (what will be obtained by quoting target field value or 
	 * target method invocation) to one returned by <code>{@link #getReplacement()}</code> method.<br />
	 * 
	 * @param proceedingJoinPoint : Join-point cross-cut by the point-cut specified with 
	 * <code>{@link #pointcutAtGettingOriginal()}</code> method.
	 * @return Either object returned by <code>{@link #getReplacement()}</code> method or original object 
	 * (what is being obtained by quoting target field value or target method invocation) according 
	 * to the setting with <code>{@link #isOnlyForNull()}</code> method.
	 * @see #isOnlyForNull()
	 */
	@Around( value = "pointcutAtGettingOriginal()")
	public Object aroundGettingOriginal( final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		final Object replacement = getReplacement();
		
		Object result = null;
			if ( !isOnlyForNull()) {
				result = replacement;
			}
			else {
				try {
					if ( proceedingJoinPoint.getArgs().length < 1) {
						result = proceedingJoinPoint.proceed();
					}
					else {
						result 
						= proceedingJoinPoint.proceed( proceedingJoinPoint.getArgs());
					}
				} 
				catch( Throwable throwable) {
					if ( RuntimeException.class.isInstance( throwable)) {
						throw (RuntimeException)throwable;
					}
					else {
						final Signature signature = proceedingJoinPoint.getSignature();
						if ( MethodSignature.class.isInstance( signature)) {
							final Method method = ((MethodSignature)signature).getMethod();
							if ( method != null) {
								for( Class<?> exceptionType : method.getExceptionTypes()) {
									if ( !exceptionType.isInstance( throwable)) continue;
									throw throwable;
								}
							}
						}
						throw new RuntimeException( throwable);
					}
				}
				
				if ( result == null) {
					result = replacement;
				}
			}
		
			final Logger logger = getLogger();
				if ( logger.isDebugEnabled()) {
					logger.debug(
							String.format(
									"Returning %1$s as result of proceeding the join-point \"%2$s\".",
									result, 
									proceedingJoinPoint.toString())
							);
				}
		
		return result;
	}
}
//TODO Change to definition by using parameterized generic abstract aspect once found out that AspectJ's annotation style supports it even with LTW.   